package net.lucode.hackware.magicindicatordemo.ext.navigator;

import net.lucode.hackware.magicindicator.NavigatorHelper;
import net.lucode.hackware.magicindicator.abs.IPagerNavigator;
import net.lucode.hackware.magicindicator.buildins.ArgbEvaluatorHolder;
import net.lucode.hackware.magicindicator.buildins.UIUtil;
import ohos.agp.animation.Animator;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentContainer;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.utils.Color;
import ohos.agp.utils.Point;
import ohos.app.Context;
import ohos.multimodalinput.event.TouchEvent;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

/**
 * 类似CircleIndicator的效果
 *
 * @since 2020-09-29
 */
public class ScaleCircleNavigator extends Component implements IPagerNavigator,
        NavigatorHelper.OnNavigatorScrollListener, Component.TouchEventListener, Component.DrawTask,
        Component.EstimateSizeListener, ComponentContainer.ArrangeListener {
    private int mMinRadius;
    private int mMaxRadius;
    private int mNormalCircleColor = Color.LTGRAY.getValue();
    private int mSelectedCircleColor = Color.GRAY.getValue();
    private int mCircleSpacing;
    private int mCircleCount;

    private Paint mPaint = new Paint();
    private List<Point> mCirclePoints = new ArrayList<Point>();
    private HashMap<Integer,Float> mCircleRadiusArray = new HashMap<Integer,Float>();

    private boolean mIsTouchable;
    private OnCircleClickListener mCircleClickListener;
    private float mDownX;
    private float mDownY;
    private int mTouchSlop;

    private boolean mIsFollowTouch = true; // 是否跟随手指滑动
    private NavigatorHelper mNavigatorHelper = new NavigatorHelper();
    private int mStartInterpolator = Animator.CurveType.LINEAR;

    /**
     * ScaleCircleNavigator
     *
     * @param context context
     */
    public ScaleCircleNavigator(Context context) {
        super(context);
        init(context);
    }

    private void init(Context context) {
        mTouchSlop = 8;
        mMinRadius = UIUtil.vp2px(context, 3);
        mMaxRadius = UIUtil.vp2px(context, 5);
        mCircleSpacing = UIUtil.vp2px(context, 8);
        mNavigatorHelper.setNavigatorScrollListener(this);
        mNavigatorHelper.setSkimOver(true);
        setTouchEventListener(this);
        addDrawTask(this);
    }

    @Override
    public boolean onEstimateSize(int widthMeasureSpec, int heightMeasureSpec) {
        prepareCirclePoints();
        setEstimatedSize(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
        addDrawTask(this);
        return false;
    }

    private int measureWidth(int widthMeasureSpec) {
        int mode = EstimateSpec.getMode(widthMeasureSpec);
        int width = EstimateSpec.getSize(widthMeasureSpec);
        int result = 0;
        switch (mode) {
            case EstimateSpec.PRECISE:
                result = width;
                break;
            case EstimateSpec.NOT_EXCEED:
            case EstimateSpec.UNCONSTRAINT:
                if (mCircleCount <= 0) {
                    result = getPaddingLeft() + getPaddingRight();
                } else {
                    result = (mCircleCount - 1) * mMinRadius * 2 + mMaxRadius * 2
                            + (mCircleCount - 1) * mCircleSpacing + getPaddingLeft() + getPaddingRight();
                }
                break;
            default:
                break;
        }
        return result;
    }

    private int measureHeight(int heightMeasureSpec) {
        int mode = EstimateSpec.getMode(heightMeasureSpec);
        int height = EstimateSpec.getSize(heightMeasureSpec);
        int result = 0;
        switch (mode) {
            case EstimateSpec.PRECISE:
                result = height;
                break;
            case EstimateSpec.NOT_EXCEED:
            case EstimateSpec.UNCONSTRAINT:
                result = mMaxRadius * 2 + getPaddingTop() + getPaddingBottom();
                break;
            default:
                break;
        }
        return result;
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        prepareCirclePoints();
        drawCircles(canvas);
        drawIndicator(canvas);
    }

    private void drawCircles(Canvas canvas) {
        for (int i = 0, j = mCirclePoints.size(); i < j; i++) {
            Point point = mCirclePoints.get(i);
            float radius = 10;
            if (mCircleRadiusArray.get(i) != null) {
                radius = mCircleRadiusArray.get(i);
            }
            mPaint.setColor(new Color(ArgbEvaluatorHolder.eval((radius - mMinRadius) / (mMaxRadius - mMinRadius),
                    mNormalCircleColor, mSelectedCircleColor)));
            canvas.drawCircle(point.getPointX(), getHeight() / 2.0f, radius, mPaint);
        }
    }

    private void drawIndicator(Canvas canvas) {
        Point point = mCirclePoints.get(0);
        float radius = mMinRadius + (mMaxRadius - mMinRadius);
        if (mCircleRadiusArray.get(0) != null) {
            radius = mCircleRadiusArray.get(0);
        }
        mPaint.setColor(new Color(ArgbEvaluatorHolder.eval((radius - mMinRadius) / (mMaxRadius - mMinRadius),
                mNormalCircleColor, mSelectedCircleColor)));
        canvas.drawCircle(point.getPointX(), getHeight() / 2.0f, radius, mPaint);
    }

    private void prepareCirclePoints() {
        mCirclePoints.clear();
        if (mCircleCount > 0) {
            int height = Math.round(getHeight() / 2.0f);
            int centerSpacing = mMinRadius * 2 + mCircleSpacing;
            int startX = mMaxRadius + getPaddingLeft();
            for (int i = 0; i < mCircleCount; i++) {
                Point pointF = new Point(startX, height);
                mCirclePoints.add(pointF);
                startX += centerSpacing;
            }
        }
    }

    @Override
    public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
        float pointerPositionX = touchEvent.getPointerPosition(touchEvent.getIndex()).getX();
        float pointerPositionY = touchEvent.getPointerPosition(touchEvent.getIndex()).getY();
        switch (touchEvent.getAction()) {
            case TouchEvent.PRIMARY_POINT_DOWN:
                if (mIsTouchable) {
                    mDownX = pointerPositionX;
                    mDownY = pointerPositionY;
                    return true;
                }
                break;
            case TouchEvent.PRIMARY_POINT_UP:
                if (mCircleClickListener != null) {
                    if (Math.abs(pointerPositionX - mDownX) <= mTouchSlop
                            && Math.abs(pointerPositionY - mDownY) <= mTouchSlop) {
                        float max = Float.MAX_VALUE;
                        int index = 0;
                        for (int i = 0; i < mCirclePoints.size(); i++) {
                            Point pointF = mCirclePoints.get(i);
                            float offset = Math.abs(pointF.getPointX() - pointerPositionX);
                            if (offset < max) {
                                max = offset;
                                index = i;
                            }
                        }
                        mCircleClickListener.onClick(index);
                    }
                }
                break;
            default:
                break;
        }
        return false;
    }

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        mNavigatorHelper.onPageScrolled(position, positionOffset, positionOffsetPixels);
        invalidate();
    }

    @Override
    public void onPageSelected(int position) {
        mNavigatorHelper.onPageSelected(position);
        invalidate();
    }

    @Override
    public void onPageScrollStateChanged(int state) {
        mNavigatorHelper.onPageScrollStateChanged(state);
    }

    @Override
    public boolean onArrange(int i, int i1, int i2, int i3) {
        prepareCirclePoints();
        return false;
    }

    @Override
    public void notifyDataSetChanged() {
        prepareCirclePoints();
        addDrawTask(this);
    }

    @Override
    public void onAttachToMagicIndicator() {
    }

    @Override
    public void onDetachFromMagicIndicator() {
    }

    /**
     * setMinRadius
     *
     * @param minRadius minRadius
     */
    public void setMinRadius(int minRadius) {
        mMinRadius = minRadius;
        prepareCirclePoints();
        addDrawTask(this);
    }

    /**
     * setMaxRadius
     *
     * @param maxRadius maxRadius
     */
    public void setMaxRadius(int maxRadius) {
        mMaxRadius = maxRadius;
        prepareCirclePoints();
        addDrawTask(this);
    }

    /**
     * setNormalCircleColor
     *
     * @param normalCircleColor normalCircleColor
     */
    public void setNormalCircleColor(int normalCircleColor) {
        mNormalCircleColor = normalCircleColor;
        invalidate();
    }

    /**
     * setSelectedCircleColor
     *
     * @param selectedCircleColor selectedCircleColor
     */
    public void setSelectedCircleColor(int selectedCircleColor) {
        mSelectedCircleColor = selectedCircleColor;
        prepareCirclePoints();
        addDrawTask(this);
    }

    /**
     * setCircleSpacing
     *
     * @param circleSpacing circleSpacing
     */
    public void setCircleSpacing(int circleSpacing) {
        mCircleSpacing = circleSpacing;
        prepareCirclePoints();
        addDrawTask(this);
    }

    /**
     * setStartInterpolator
     *
     * @param startInterpolator startInterpolator
     */
    public void setStartInterpolator(int startInterpolator) {
        mStartInterpolator = startInterpolator;
    }

    /**
     * getStartInterpolator
     *
     * @return mStartInterpolator
     */
    public int getStartInterpolator() {
        return mStartInterpolator;
    }

    /**
     * setCircleCount
     *
     * @param count count
     */
    public void setCircleCount(int count) {
        mCircleCount = count; // 此处不调用invalidate，让外部调用notifyDataSetChanged
        mNavigatorHelper.setTotalCount(mCircleCount);
    }

    /**
     * setTouchable
     *
     * @param isTouchable isTouchable
     */
    public void setTouchable(boolean isTouchable) {
        mIsTouchable = isTouchable;
    }

    /**
     * setFollowTouch
     *
     * @param isFollowTouch isFollowTouch
     */
    public void setFollowTouch(boolean isFollowTouch) {
        mIsFollowTouch = isFollowTouch;
    }

    /**
     * setSkimOver
     *
     * @param isSkimOver isSkimOver
     */
    public void setSkimOver(boolean isSkimOver) {
        mNavigatorHelper.setSkimOver(isSkimOver);
    }

    /**
     * setCircleClickListener
     *
     * @param circleClickListener circleClickListener
     */
    public void setCircleClickListener(OnCircleClickListener circleClickListener) {
        if (!mIsTouchable) {
            mIsTouchable = true;
        }
        mCircleClickListener = circleClickListener;
    }

    @Override
    public void onEnter(int index, int totalCount, float enterPercent, boolean isLeftToRight) {
        if (mIsFollowTouch) {
            float radius = mMinRadius + (mMaxRadius - mMinRadius) * enterPercent;
            mCircleRadiusArray.put(index, radius);
            prepareCirclePoints();
            addDrawTask(this);
        }
    }

    @Override
    public void onLeave(int index, int totalCount, float leavePercent, boolean isLeftToRight) {
        if (mIsFollowTouch) {
            float radius = mMaxRadius + (mMinRadius - mMaxRadius) * leavePercent;
            mCircleRadiusArray.put(index, radius);
            prepareCirclePoints();
            addDrawTask(this);
        }
    }

    @Override
    public void onSelected(int index, int totalCount) {
        if (!mIsFollowTouch) {
            mCircleRadiusArray.put(index, (float) mMaxRadius);
            prepareCirclePoints();
            addDrawTask(this);
        }
    }

    @Override
    public void onDeselected(int index, int totalCount) {
        if (!mIsFollowTouch) {
            mCircleRadiusArray.put(index, (float) mMinRadius);
            prepareCirclePoints();
            addDrawTask(this);
        }
    }

    /**
     * OnCircleClickListener interface
     *
     * @since 2020-09-29
     */
    public interface OnCircleClickListener {
        /**
         * onClick
         *
         * @param index index
         */
        void onClick(int index);
    }
}
